using EasyXaf.LowCode.WorkflowEditors.Models;
using EasyXaf.LowCode.WorkflowEditors.Models.Activities;
using EasyXaf.LowCode.WorkflowEditors.UndoManagers;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Collections.Concurrent;

namespace EasyXaf.LowCode.WorkflowEditors;

public partial class WorkflowRenderer : IAsyncDisposable
{
    private string WorkflowEditorEleId { get; } = Guid.NewGuid().ToString();

    private WorkflowToolbar WorkflowToolbar { get; set; }

    private DotNetObjectReference<WorkflowRenderer> DotNetObjectRef { get; set; }

    private IJSObjectReference JsModule { get; set; }

    private ConcurrentBag<Action> Actions { get; set; } = new();

    private double Zoom { get; set; } = 1;

    private bool PanAndZoomPanelVisible { get; set; }

    private Workflow Workflow { get; set; }

    private IUndoManager UndoManager { get; set; }

    [Parameter]
    public WorkflowModel Model { get; set; }

    [Inject]
    private IJSRuntime JSRuntime { get; set; }

    [JSInvokable]
    public async Task SetZoomAsync(double zoom)
    {
        if (Zoom != zoom)
        {
            Zoom = zoom;
            await WorkflowToolbar.RaiseZoomChangedAsync(zoom);
        }
    }

    private ContainerActivity CreateContainerActivity(Workflow workflow, ISequence sequence)
    {
        var containerActivity = new ContainerActivity(workflow, sequence);
        containerActivity.OnCreated();
        return containerActivity;
    }

    private SwitchActivity CreateSwitchActivity(Workflow workflow, ISequence sequence)
    {
        var switchActivity = new SwitchActivity(workflow, sequence);
        switchActivity.OnCreated();

        switchActivity.Branches[0].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[0]));
        switchActivity.Branches[0].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[0]));

        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));
        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));
        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));
        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));
        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));
        switchActivity.Branches[1].Activities.Insert(1, CreateContainerActivity(workflow, switchActivity.Branches[1]));

        var whenBranch = new WhenBranch(workflow, switchActivity);
        whenBranch.Activities.Add(new StartActivity(workflow, whenBranch));
        whenBranch.Activities.Add(CreateContainerActivity(workflow, whenBranch));
        whenBranch.Activities.Add(CreateContainerActivity(workflow, whenBranch));
        whenBranch.Activities.Add(new EndActivity(workflow, whenBranch));

        var whenBranch2 = new WhenBranch(workflow, switchActivity);
        whenBranch2.Activities.Add(new StartActivity(workflow, whenBranch2));
        whenBranch2.Activities.Add(CreateContainerActivity(workflow, whenBranch2));
        whenBranch2.Activities.Add(CreateContainerActivity(workflow, whenBranch2));
        whenBranch2.Activities.Add(CreateContainerActivity(workflow, whenBranch2));
        whenBranch2.Activities.Add(new EndActivity(workflow, whenBranch2));

        var whenBranch3 = new WhenBranch(workflow, switchActivity);
        whenBranch3.Activities.Add(new StartActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(CreateContainerActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(CreateContainerActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(CreateContainerActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(CreateContainerActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(CreateContainerActivity(workflow, whenBranch3));
        whenBranch3.Activities.Add(new EndActivity(workflow, whenBranch3));

        var whenBranch4 = new WhenBranch(workflow, switchActivity);
        whenBranch4.Activities.Add(new StartActivity(workflow, whenBranch4));
        whenBranch4.Activities.Add(CreateContainerActivity(workflow, whenBranch4));
        whenBranch4.Activities.Add(CreateContainerActivity(workflow, whenBranch4));
        whenBranch4.Activities.Add(new EndActivity(workflow, whenBranch4));

        switchActivity.Branches.Insert(0, whenBranch);
        switchActivity.Branches.Insert(0, whenBranch2);
        switchActivity.Branches.Insert(0, whenBranch3);
        switchActivity.Branches.Insert(0, whenBranch4);

        return switchActivity;
    }

    private void ContainerChanged(IContainer container)
    {
        UndoManager.ChangeCurrentContainer(Workflow, container);
    }

    private async Task ZoomChangedAsync(double zoom)
    {
        Zoom = zoom;
        await JsModule.InvokeVoidAsync("zoomTo", WorkflowEditorEleId, zoom);
    }

    private async Task BestFitAsync()
    {
        var zoom = await JsModule.InvokeAsync<double?>("bestFit", WorkflowEditorEleId);
        if (zoom.HasValue)
        {
            Zoom = zoom.Value;
        }
    }

    private async Task FitToDrawingAsync()
    {
        var zoom = await JsModule.InvokeAsync<double?>("fitToDrawing", WorkflowEditorEleId);
        if (zoom.HasValue)
        {
            Zoom = zoom.Value;
        }
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        UndoManager = new UndoManager();

        Workflow = new Workflow();
        Workflow.CurrentContainerChanged += Workflow_CurrentContainerChanged;
        Workflow.WorkflowChanged += Workflow_WorkflowChanged;
        Workflow.Activities.Add(new StartActivity(Workflow, Workflow));

        var switchActivity = CreateSwitchActivity(Workflow, Workflow);
        switchActivity.Branches[0].Activities.Insert(switchActivity.Branches[0].Activities.Count - 2, CreateSwitchActivity(Workflow, switchActivity.Branches[0]));
        switchActivity.Branches[2].Activities.Insert(switchActivity.Branches[2].Activities.Count - 3, CreateSwitchActivity(Workflow, switchActivity.Branches[2]));
        switchActivity.Branches[3].Activities.Insert(1, CreateSwitchActivity(Workflow, switchActivity.Branches[3]));

        Workflow.Activities.Add(switchActivity);
        Workflow.Activities.Add(CreateContainerActivity(Workflow, Workflow));
        Workflow.Activities.Add(new EndActivity(Workflow, Workflow));
    }

    private void Workflow_CurrentContainerChanged(object sender, EventArgs e)
    {
        InvokeAsync(() =>
        {
            Actions.Add(async () =>
            {
                await BestFitAsync();
                await WorkflowToolbar.RaiseZoomChangedAsync(Zoom);
            });
            StateHasChanged();
        });
    }

    private void Workflow_WorkflowChanged(object sender, EventArgs e)
    {
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            var jsModuleFile = "./_content/EasyXaf.LowCode.WorkflowEditors/WorkflowRenderer.razor.js";
            JsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", jsModuleFile);
            DotNetObjectRef = DotNetObjectReference.Create(this);
            await JsModule.InvokeVoidAsync("init", WorkflowEditorEleId, DotNetObjectRef);
        }

        if (Actions.Any())
        {
            foreach (var action in Actions)
            {
                action.Invoke();
            }
            Actions.Clear();
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (UndoManager != null)
        {
            UndoManager.Dispose();
            UndoManager = null;
        }

        if (Workflow != null)
        {
            Workflow.CurrentContainerChanged -= Workflow_CurrentContainerChanged;
            Workflow.WorkflowChanged -= Workflow_WorkflowChanged;
            Workflow = null;
        }

        try
        {
            if (JsModule != null)
            {
                await JsModule.InvokeVoidAsync("dispose", WorkflowEditorEleId);
                await JsModule.DisposeAsync();
                JsModule = null;
            }
        }
        catch (JSDisconnectedException)
        {
        }

        DotNetObjectRef?.Dispose();
        DotNetObjectRef = null;

        GC.SuppressFinalize(this);
    }
}